Fix some warnings
[adiumx.git] / Plugins / Gaim Service / SLGaimCocoaAdapter.m
blob0a1579508014afa2f8077511c349ebfe41f9a04a
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "SLGaimCocoaAdapter.h"
19 #import <Adium/AIAccountControllerProtocol.h>
21 #import <Adium/AIInterfaceControllerProtocol.h>
22 #import <Adium/AILoginControllerProtocol.h>
23 #import "CBGaimAccount.h"
24 #import "CBGaimServicePlugin.h"
25 #import "adiumGaimCore.h"
26 #import "adiumGaimEventloop.h"
27 #import "UndeclaredLibgaimFunctions.h"
28 #import <AIUtilities/AIObjectAdditions.h>
29 #import <Adium/AIAccount.h>
30 #import <Adium/AICorePluginLoader.h>
31 #import <Adium/AIService.h>
32 #import <Adium/AIChat.h>
33 #import <Adium/AIContentTyping.h>
34 #import <Adium/AIHTMLDecoder.h>
35 #import <Adium/AIListContact.h>
36 #import <Adium/NDRunLoopMessenger.h>
38 #import <CoreFoundation/CFRunLoop.h>
39 #import <CoreFoundation/CFSocket.h>
40 #include <Libgaim/libgaim.h>
41 #include <glib.h>
42 #include <stdlib.h>
44 #ifndef JOSCAR_SUPERCEDE_LIBGAIM
45         #import "ESGaimAIMAccount.h"
46         #import "CBGaimOscarAccount.h"
47 #endif
49 //Gaim slash command interface
50 #include <Libgaim/cmds.h>
52 @interface SLGaimCocoaAdapter (PRIVATE)
53 - (void)initLibGaim;
54 - (BOOL)attemptGaimCommandOnMessage:(NSString *)originalMessage fromAccount:(AIAccount *)sourceAccount inChat:(AIChat *)chat;
55 - (void)refreshAutoreleasePool:(NSTimer *)inTimer;
56 @end
59  * A pointer to the single instance of this class active in the application.
60  * The gaim callbacks need to be C functions with specific prototypes, so they
61  * can't be ObjC methods. The ObjC callbacks do need to be ObjC methods. This
62  * allows the C ones to call the ObjC ones.
63  **/
64 static SLGaimCocoaAdapter   *sharedInstance = nil;
66 //Dictionaries to track gaim<->adium interactions
67 NSMutableDictionary *accountDict = nil;
68 //NSMutableDictionary *contactDict = nil;
69 NSMutableDictionary *chatDict = nil;
71 //The autorelease pool presently in use; it will be periodically released and recreated
72 static NSAutoreleasePool *currentAutoreleasePool = nil;
73 #define AUTORELEASE_POOL_REFRESH        5.0
75 static NSMutableArray   *libgaimPluginArray = nil;
77 @implementation SLGaimCocoaAdapter
79 /*!
80  * @brief Return the shared instance
81  */
82 + (SLGaimCocoaAdapter *)sharedInstance
83 {       
84         @synchronized(self) {
85                 if (!sharedInstance) {
86                         sharedInstance = [[self alloc] init];
87                 }
88         }
90         return sharedInstance;
93 /*!
94  * @brief Plugin loaded
95  *
96  * Initialize each libgaim plugin.  These plugins should not do anything within libgaim itself; this should be done in
97  * -[plugin initLibgaimPlugin].
98  */
99 + (void)pluginDidLoad
101         NSEnumerator    *enumerator;
102         NSString                *libgaimPluginPath;
104         libgaimPluginArray = [[NSMutableArray alloc] init];
105         
106         enumerator = [[[AIObject sharedAdiumInstance] allResourcesForName:@"Plugins"
107                                                                                                            withExtensions:@"AdiumLibgaimPlugin"] objectEnumerator];
108         while ((libgaimPluginPath = [enumerator nextObject])) {
109                 [AICorePluginLoader loadPluginAtPath:libgaimPluginPath
110                                                           confirmLoading:YES
111                                                                  pluginArray:libgaimPluginArray];
112         }
115 + (NSArray *)libgaimPluginArray
117         return libgaimPluginArray;
120 //Register the account gaimside in the gaim thread
121 - (void)addAdiumAccount:(CBGaimAccount *)adiumAccount
123         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
124         account->ui_data = [adiumAccount retain];
125         
126         gaim_accounts_add(account);
127         gaim_account_set_status_list(account, "offline", YES, NULL);
130 //Remove an account gaimside
131 - (void)removeAdiumAccount:(CBGaimAccount *)adiumAccount
133         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
135         [(CBGaimAccount *)account->ui_data release];
136         account->ui_data = nil;
137         
138     gaim_accounts_remove(account);      
141 #pragma mark Initialization
142 - (id)init
144         if ((self = [super init])) {
145                 accountDict = [[NSMutableDictionary alloc] init];
146                 chatDict = [[NSMutableDictionary alloc] init];
148                 [self initLibGaim];             
149         }
150         
151     return self;
155  * @brief Empty and recreate the autorelease pool
157  * Our autoreleased objects will only be released when the outermost autorelease pool is released.
158  * This is handled automatically in the main thread, but we need to do it manually here.
159  */
160 - (void)refreshAutoreleasePool:(NSTimer *)inTimer
162         [currentAutoreleasePool release];
163         currentAutoreleasePool = [[NSAutoreleasePool alloc] init];
166 static void ZombieKiller_Signal(int i)
168         int status;
169         pid_t child_pid;
171         while ((child_pid = waitpid(-1, &status, WNOHANG)) > 0);
174 - (void)initLibGaim
175 {       
176         //Set the gaim user directory to be within this user's directory
177         NSString        *gaimUserDir = [[[adium loginController] userDirectory] stringByAppendingPathComponent:@"libgaim"];
178         gaim_util_set_user_dir([[gaimUserDir stringByExpandingTildeInPath] UTF8String]);
180         /* Delete blist.xml once when 1.0 runs to clear out any old silliness */
181         if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Adium 1.0 deleted blist.xml"]) {
182                 [[NSFileManager defaultManager] removeFileAtPath:
183                         [[[NSString stringWithUTF8String:gaim_user_dir()] stringByAppendingPathComponent:@"blist"] stringByAppendingPathExtension:@"xml"]
184                                                                                                  handler:nil];
185                 [[NSUserDefaults standardUserDefaults] setBool:YES
186                                                                                                 forKey:@"Adium 1.0 deleted blist.xml"];
187         }
189         gaim_core_set_ui_ops(adium_gaim_core_get_ops());
190         gaim_eventloop_set_ui_ops(adium_gaim_eventloop_get_ui_ops());
192         //Initialize the libgaim core; this will call back on the function specified in our core UI ops for us to finish configuring libgaim
193         if (!gaim_core_init("Adium")) {
194                 NSLog(@"*** FATAL ***: Failed to initialize gaim core");
195                 GaimDebug (@"*** FATAL ***: Failed to initialize gaim core");
196         }
197         
198         //Libgaim's async DNS lookup tends to create zombies.
199         {
200                 struct sigaction act;
201                 
202                 act.sa_handler = ZombieKiller_Signal;           
203                 //Send for terminated but not stopped children
204                 act.sa_flags = SA_NOCLDWAIT;
206                 sigaction(SIGCHLD, &act, NULL);
207         }
210 #pragma mark Lookup functions
212 NSString* serviceClassForGaimProtocolID(const char *protocolID)
214         NSString        *serviceClass = nil;
215         if (protocolID) {
216                 if (!strcmp(protocolID, "prpl-oscar"))
217                         serviceClass = @"AIM-compatible";
218                 else if (!strcmp(protocolID, "prpl-gg"))
219                         serviceClass = @"Gadu-Gadu";
220                 else if (!strcmp(protocolID, "prpl-jabber"))
221                         serviceClass = @"Jabber";
222                 else if (!strcmp(protocolID, "prpl-meanwhile"))
223                         serviceClass = @"Sametime";
224                 else if (!strcmp(protocolID, "prpl-msn"))
225                         serviceClass = @"MSN";
226                 else if (!strcmp(protocolID, "prpl-novell"))
227                         serviceClass = @"GroupWise";
228                 else if (!strcmp(protocolID, "prpl-yahoo"))
229                         serviceClass = @"Yahoo!";
230                 else if (!strcmp(protocolID, "prpl-zephyr"))
231                         serviceClass = @"Zephyr";
232         }
233         
234         return serviceClass;
238  * Finds an instance of CBGaimAccount for a pointer to a GaimAccount struct.
239  */
240 CBGaimAccount* accountLookup(GaimAccount *acct)
242         CBGaimAccount *adiumGaimAccount = (acct ? (CBGaimAccount *)acct->ui_data : nil);
243         /* If the account doesn't have its ui_data associated yet (we haven't tried to connect) but we want this
244          * lookup data, we have to do some manual parsing.  This is used for example from the OTR preferences.
245          */
246         if (!adiumGaimAccount && acct) {
247                 const char      *protocolID = acct->protocol_id;
248                 NSString        *serviceClass = serviceClassForGaimProtocolID(protocolID);
250                 NSEnumerator    *enumerator = [[[[AIObject sharedAdiumInstance] accountController] accounts] objectEnumerator];
251                 while ((adiumGaimAccount = [enumerator nextObject])) {
252                         if ([adiumGaimAccount isKindOfClass:[CBGaimAccount class]] &&
253                            [[[adiumGaimAccount service] serviceClass] isEqualToString:serviceClass] &&
254                            [[adiumGaimAccount UID] caseInsensitiveCompare:[NSString stringWithUTF8String:acct->username]] == NSOrderedSame) {
255                                 break;
256                         }
257                 }
258         }
259     return adiumGaimAccount;
262 GaimAccount* accountLookupFromAdiumAccount(CBGaimAccount *adiumAccount)
264         return [adiumAccount gaimAccount];
267 AIListContact* contactLookupFromBuddy(GaimBuddy *buddy)
269         //Get the node's ui_data
270         AIListContact *theContact = (buddy ? (AIListContact *)buddy->node.ui_data : nil);
272         //If the node does not have ui_data yet, we need to create a contact and associate it
273         if (!theContact && buddy) {
274                 NSString        *UID;
275         
276                 UID = [NSString stringWithUTF8String:gaim_normalize(buddy->account, buddy->name)];
277                 
278                 theContact = [accountLookup(buddy->account) contactWithUID:UID];
279                 
280                 //Associate the handle with ui_data and the buddy with our statusDictionary
281                 buddy->node.ui_data = [theContact retain];
282         }
283         
284         return theContact;
287 AIListContact* contactLookupFromIMConv(GaimConversation *conv)
289         return nil;
292 AIChat* groupChatLookupFromConv(GaimConversation *conv)
294         AIChat *chat;
295         
296         chat = (AIChat *)conv->ui_data;
297         if (!chat) {
298                 NSString *name = [NSString stringWithUTF8String:conv->name];
299                 
300                 chat = [accountLookup(conv->account) chatWithName:name];
302                 [chatDict setObject:[NSValue valueWithPointer:conv] forKey:[chat uniqueChatID]];
303                 conv->ui_data = [chat retain];
304         }
306         return chat;
309 AIChat* existingChatLookupFromConv(GaimConversation *conv)
311         return (conv ? conv->ui_data : nil);
314 AIChat* chatLookupFromConv(GaimConversation *conv)
316         switch(gaim_conversation_get_type(conv)) {
317                 case GAIM_CONV_TYPE_CHAT:
318                         return groupChatLookupFromConv(conv);
319                         break;
320                 case GAIM_CONV_TYPE_IM:
321                         return imChatLookupFromConv(conv);
322                         break;
323                 default:
324                         return existingChatLookupFromConv(conv);
325                         break;
326         }
329 AIChat* imChatLookupFromConv(GaimConversation *conv)
331         AIChat                  *chat;
332         
333         chat = (AIChat *)conv->ui_data;
335         if (!chat) {
336                 //No chat is associated with the IM conversation
337                 AIListContact   *sourceContact;
338                 GaimBuddy               *buddy;
339                 GaimAccount             *account;
340                 
341                 account = conv->account;
342 //              GaimDebug (@"%x conv->name %s; normalizes to %s",account,conv->name,gaim_normalize(account,conv->name));
344                 //First, find the GaimBuddy with whom we are conversing
345                 buddy = gaim_find_buddy(account, conv->name);
346                 if (!buddy) {
347                         GaimDebug (@"imChatLookupFromConv: Creating %s %s",account->username,gaim_normalize(account,conv->name));
348                         //No gaim_buddy corresponding to the conv->name is on our list, so create one
349                         buddy = gaim_buddy_new(account, gaim_normalize(account, conv->name), NULL);     //create a GaimBuddy
350                 }
352                 NSCAssert(buddy != nil, @"buddy was nil");
353                 
354                 sourceContact = contactLookupFromBuddy(buddy);
356                 // Need to start a new chat, associating with the GaimConversation
357                 chat = [accountLookup(account) chatWithContact:sourceContact];
359                 if (!chat) {
360                         NSString        *errorString;
362                         errorString = [NSString stringWithFormat:@"conv %x: Got nil chat in lookup for sourceContact %@ (%x ; \"%s\" ; \"%s\") on adiumAccount %@ (%x ; \"%s\")",
363                                 conv,
364                                 sourceContact,
365                                 buddy,
366                                 (buddy ? buddy->name : ""),
367                                 ((buddy && buddy->account && buddy->name) ? gaim_normalize(buddy->account, buddy->name) : ""),
368                                 accountLookup(account),
369                                 account,
370                                 (account ? account->username : "")];
372                         NSCAssert(chat != nil, errorString);
373                 }
375                 //Associate the GaimConversation with the AIChat
376                 [chatDict setObject:[NSValue valueWithPointer:conv] forKey:[chat uniqueChatID]];
377                 conv->ui_data = [chat retain];
378         }
380         return chat;    
383 GaimConversation* convLookupFromChat(AIChat *chat, id adiumAccount)
385         GaimConversation        *conv = [[chatDict objectForKey:[chat uniqueChatID]] pointerValue];
386         GaimAccount                     *account = accountLookupFromAdiumAccount(adiumAccount);
387         
388         if (!conv && adiumAccount) {
389                 AIListObject *listObject = [chat listObject];
390                 
391                 //If we have a listObject, we are dealing with a one-on-one chat, so proceed accordingly
392                 if (listObject) {
393                         char *destination;
394                         
395                         destination = g_strdup(gaim_normalize(account, [[listObject UID] UTF8String]));
396                         
397                         conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, destination);
398                         
399                         //associate the AIChat with the gaim conv
400                         if (conv) imChatLookupFromConv(conv);
402                         g_free(destination);
403                         
404                 } else {
405                         //Otherwise, we have a multiuser chat.
406                         
407                         //All multiuser chats should have a non-nil name.
408                         NSString        *chatName = [chat name];
409                         if (chatName) {
410                                 const char *name = [chatName UTF8String];
411                                 
412                                 /*
413                                  Look for an existing gaimChat.  If we find one, our job is complete.
414                                  
415                                  We will never find one if we are joining a chat on our own (via the Join Chat dialogue).
416                                  
417                                  We should never get to this point if we were invited to a chat, as groupChatLookupFromConv(),
418                                  which was called when we accepted the invitation and got the chat information from Gaim,
419                                  will have associated the GaimConversation with the chat and we would have stopped after
420                                  [[chatDict objectForKey:[chat uniqueChatID]] pointerValue] above.
421                                  
422                                  However, there's no reason not to check just in case.
423                                  */
424                                 GaimChat *gaimChat = gaim_blist_find_chat (account, name);
425                                 if (!gaimChat) {
426                                         
427                                         /*
428                                          If we don't have a GaimChat with this name on this account, we need to create one.
429                                          Our chat, which should have been created via the Adium Join Chat API, should have
430                                          a ChatCreationInfo status object with the information we need to ask Gaim to
431                                          perform the join.
432                                          */
433                                         NSDictionary    *chatCreationInfo = [chat statusObjectForKey:@"ChatCreationInfo"];
434                                         
435                                         GaimDebug (@"Creating a chat.");
437                                         GHashTable                              *components;
438                                         
439                                         //Prpl Info
440                                         GaimConnection                  *gc = gaim_account_get_connection(account);
441                                         GList                                   *list, *tmp;
442                                         struct proto_chat_entry *pce;
443                                         NSString                                *identifier;
444                                         NSEnumerator                    *enumerator;
445                                         
446                                         //Create a hash table
447                                         //The hash table should contain char* objects created via a g_strdup method
448                                         components = g_hash_table_new_full(g_str_hash, g_str_equal,
449                                                                                                            g_free, g_free);
450                                         
451                                         enumerator = [chatCreationInfo keyEnumerator];
452                                         while ((identifier = [enumerator nextObject])) {
453                                                 id              value = [chatCreationInfo objectForKey:identifier];
454                                                 char    *valueUTF8String = NULL;
455                                                 
456                                                 if ([value isKindOfClass:[NSNumber class]]) {
457                                                         valueUTF8String = g_strdup_printf("%d",[value intValue]);
459                                                 } else if ([value isKindOfClass:[NSString class]]) {
460                                                         valueUTF8String = g_strdup([value UTF8String]);
462                                                 } else {
463                                                         GaimDebug (@"Invalid value %@ for identifier %@",value,identifier);
464                                                 }
465                                                 
466                                                 //Store our chatCreationInfo-supplied value in the compnents hash table
467                                                 if (valueUTF8String) {
468                                                         g_hash_table_replace(components,
469                                                                                                  g_strdup([identifier UTF8String]),
470                                                                                                  valueUTF8String);
471                                                 }
472                                         }
474                                         //In debug mode, verify we didn't miss any required values
475                                         if (GAIM_DEBUG) {
476                                                 /*
477                                                  Get the chat_info for our desired account.  This will be a GList of proto_chat_entry
478                                                  objects, each of which has a label and identifier.  Each may also have is_int, with a minimum
479                                                  and a maximum integer value.
480                                                  */
481                                                 if ((GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl))->chat_info)
482                                                 {
483                                                         list = (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl))->chat_info(gc);
485                                                         //Look at each proto_chat_entry in the list to verify we have it in chatCreationInfo
486                                                         for (tmp = list; tmp; tmp = tmp->next)
487                                                         {
488                                                                 pce = tmp->data;
489                                                                 char    *identifier = g_strdup(pce->identifier);
490                                                                 
491                                                                 NSString        *value = [chatCreationInfo objectForKey:[NSString stringWithUTF8String:identifier]];
492                                                                 if (!value) {
493                                                                         GaimDebug (@"Danger, Will Robinson! %s is in the proto_info but can't be found in %@",identifier,chatCreationInfo);
494                                                                 }
495                                                         }
496                                                 }
497                                         }
499                                         /*
500                                          //Add the GaimChat to our local buddy list?
501                                         gaimChat = gaim_chat_new(account,
502                                                                                          name,
503                                                                                          components);
504                                         if ((group = gaim_find_group(group_name)) == NULL) {
505                                                 group = gaim_group_new(group_name);
506                                                 gaim_blist_add_group(group, NULL);
507                                         }
508                                         
509                                         if (gaimChat != NULL) {
510                                                 gaim_blist_add_chat(gaimChat, group, NULL);
511                                         }
512                                         */
514                                         //Join the chat serverside - the GHashTable components, couple with the originating GaimConnect,
515                                         //now contains all the information the prpl will need to process our request.
516                                         GaimDebug (@"In the event of an emergency, your GHashTable may be used as a flotation device...");
517                                         serv_join_chat(gc, components);
518                                 }
519                         }
520                 }
521         }
522         
523         return conv;
526 GaimConversation* existingConvLookupFromChat(AIChat *chat)
528         return (GaimConversation *)[[chatDict objectForKey:[chat uniqueChatID]] pointerValue];
531 void* adium_gaim_get_handle(void)
533         static int adium_gaim_handle;
534         
535         return &adium_gaim_handle;
538 NSMutableDictionary* get_chatDict(void)
540         return chatDict;
543 #pragma mark Images
545 static NSString* _messageImageCachePath(int imageID, AIAccount* adiumAccount)
547     NSString    *messageImageCacheFilename = [NSString stringWithFormat:@"TEMP-Image_%@_%i", [adiumAccount internalObjectID], imageID];
548     return [[[[AIObject sharedAdiumInstance] cachesPath] stringByAppendingPathComponent:messageImageCacheFilename] stringByAppendingPathExtension:@"png"];
551 NSString* processGaimImages(NSString* inString, AIAccount* adiumAccount)
553         NSScanner                       *scanner;
554     NSString                    *chunkString = nil;
555     NSMutableString             *newString;
556         NSString                        *targetString = @"<IMG ID=";
557         NSCharacterSet          *quoteApostropheCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\"\'"];
558     int imageID;
559         
560         if ([inString rangeOfString:targetString options:NSCaseInsensitiveSearch].location == NSNotFound) {
561                 return inString;
562         }
563         
564     //set up
565         newString = [[NSMutableString alloc] init];
566         
567     scanner = [NSScanner scannerWithString:inString];
568     [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]];
569         
570         //A gaim image tag takes the form <IMG ID='12'></IMG> where 12 is the reference for use in GaimStoredImage* gaim_imgstore_get(int)
571         
572         //Parse the incoming HTML
573     while (![scanner isAtEnd]) {
574                 
575                 //Find the beginning of a gaim IMG ID tag
576                 if ([scanner scanUpToString:targetString intoString:&chunkString]) {
577                         [newString appendString:chunkString];
578                 }
579                 
580                 if ([scanner scanString:targetString intoString:&chunkString]) {
581                         //Skip past a quote or apostrophe
582                         [scanner scanCharactersFromSet:quoteApostropheCharacterSet intoString:NULL];
583                         
584                         //Get the image ID from the tag
585                         [scanner scanInt:&imageID];
587                         //Skip past a quote or apostrophe
588                         [scanner scanCharactersFromSet:quoteApostropheCharacterSet intoString:NULL];
590                         //Scan past a >
591                         [scanner scanString:@">" intoString:nil];
592                         
593                         //Get the image, then write it out as a png
594                         GaimStoredImage         *gaimImage = gaim_imgstore_get(imageID);
595                         if (gaimImage) {
596                                 NSString                *filename = (gaim_imgstore_get_filename(gaimImage) ?
597                                                                                          [NSString stringWithUTF8String:gaim_imgstore_get_filename(gaimImage)] :
598                                                                                          @"Image");
599                                 NSString                *imagePath = _messageImageCachePath(imageID, adiumAccount);
600                                 
601                                 //First make an NSImage, then request a TIFFRepresentation to avoid an obscure bug in the PNG writing routines
602                                 //Exception: PNG writer requires compacted components (bits/component * components/pixel = bits/pixel)
603                                 NSImage                         *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:gaim_imgstore_get_data(gaimImage)
604                                                                                                                                                                                   length:gaim_imgstore_get_size(gaimImage)]];
605                                 NSData                          *imageTIFFData = [image TIFFRepresentation];
606                                 NSBitmapImageRep        *bitmapRep = [NSBitmapImageRep imageRepWithData:imageTIFFData];
607                                 
608                                 //If writing the PNG file is successful, write an <IMG SRC="filepath"> tag to our string; the 'scaledToFitImage' class lets us apply CSS to directIM images only
609                                 if ([[bitmapRep representationUsingType:NSPNGFileType properties:nil] writeToFile:imagePath atomically:YES]) {
610                                         [newString appendString:[NSString stringWithFormat:@"<IMG CLASS=\"scaledToFitImage\" SRC=\"%@\" ALT=\"%@\">",
611                                                 imagePath, filename]];
612                                 }
613                                 
614                                 [image release];
615                         } else {
616                                 //If we didn't get a gaimImage, just leave the tag for now.. maybe it was important?
617                                 [newString appendFormat:@"<IMG ID=\"%i\">",chunkString];
618                         }
619                 }
620         }
622         return ([newString autorelease]);
625 #pragma mark Notify
626 // Notify ----------------------------------------------------------------------------------------------------------
627 // We handle the notify messages within SLGaimCocoaAdapter so we can use our localized string macro
628 - (void *)handleNotifyMessageOfType:(GaimNotifyType)type withTitle:(const char *)title primary:(const char *)primary secondary:(const char *)secondary;
630     NSString *primaryString = [NSString stringWithUTF8String:primary];
631         NSString *secondaryString = secondary ? [NSString stringWithUTF8String:secondary] : nil;
632         
633         NSString *titleString;
634         if (title) {
635                 titleString = [NSString stringWithFormat:AILocalizedString(@"Adium Notice: %@",nil),[NSString stringWithUTF8String:title]];
636         } else {
637                 titleString = AILocalizedString(@"Adium : Notice", nil);
638         }
639         
640         NSString *errorMessage = nil;
641         NSString *description = nil;
642         
643         if (primaryString) {
644                 if (([primaryString rangeOfString:@"Already there"].location != NSNotFound)) {
645                         return adium_gaim_get_handle();
646                 }
647         }
649         //Suppress notification warnings we have no interest in seeing
650         if (secondaryString) {
651                 if (([secondaryString rangeOfString:@"Could not add the buddy 1 for an unknown reason"].location != NSNotFound) ||
652                         ([secondaryString rangeOfString:@"Your screen name is currently formatted as follows"].location != NSNotFound) ||
653                         ([secondaryString rangeOfString:@"Error reading from Switchboard server"].location != NSNotFound) ||
654                         ([secondaryString rangeOfString:@"0x001a: Unknown error"].location != NSNotFound) ||
655                         ([secondaryString rangeOfString:@"Not supported by host"].location != NSNotFound) ||
656                         ([secondaryString rangeOfString:@"Not logged in"].location != NSNotFound)) {
657                         return adium_gaim_get_handle();
658                 }
659         }
661     if ([primaryString rangeOfString: @"Yahoo! message did not get sent."].location != NSNotFound) {
662                 //Yahoo send error
663                 errorMessage = AILocalizedString(@"Your Yahoo! message did not get sent.", nil);
664                 
665         } else if ([primaryString rangeOfString: @"did not get sent"].location != NSNotFound) {
666                 //Oscar send error
667                 NSString *targetUserName = [[[[primaryString componentsSeparatedByString:@" message to "] objectAtIndex:1] componentsSeparatedByString:@" did not get "] objectAtIndex:0];
668                 
669                 errorMessage = [NSString stringWithFormat:AILocalizedString(@"Your message to %@ did not get sent",nil),targetUserName];
670                 
671                 if ([secondaryString rangeOfString:@"Rate"].location != NSNotFound) {
672                         description = AILocalizedString(@"You are sending messages too quickly; wait a moment and try again.",nil);
673                 } else if ([secondaryString isEqualToString:@"Service unavailable"] || [secondaryString isEqualToString:@"Not logged in"]) {
674                         description = AILocalizedString(@"Connection error.",nil);
675                 } else if ([secondaryString isEqualToString:@"Refused by client"]) {
676                         description = AILocalizedString(@"Your message was refused by the other user.",nil);
677                 } else if ([secondaryString isEqualToString:@"Reply too big"]) {
678                         description = AILocalizedString(@"Your message was too big.",nil);
679                 } else if ([secondaryString isEqualToString:@"In local permit/deny"]) {
680                         description = AILocalizedString(@"The other user is in your deny list.",nil);
681                 } else if ([secondaryString rangeOfString:@"Too evil"].location != NSNotFound) {
682                         description = AILocalizedString(@"Warning level is too high.",nil);
683                 } else if ([secondaryString isEqualToString:@"User temporarily unavailable"]) {
684                         description = AILocalizedString(@"The other user is temporarily unavailable.",nil);
685                 } else {
686                         description = AILocalizedString(@"No reason was given.",nil);
687                 }
688                 
689     } else if ([primaryString rangeOfString: @"Authorization Denied"].location != NSNotFound) {
690                 //Authorization denied; grab the user name and reason
691                 NSArray         *parts = [[[secondaryString componentsSeparatedByString:@" user "] objectAtIndex:1] componentsSeparatedByString:@" has denied your request to add them to your buddy list for the following reason:\n"];
692                 NSString        *targetUserName =  [parts objectAtIndex:0];
693                 NSString        *reason = ([parts count] > 1 ? [parts objectAtIndex:1] : AILocalizedString(@"(No reason given)",nil));
694                 
695                 errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ denied authorization:","User deined authorization; the next line has an explanation."),targetUserName];
696                 description = reason;
698     } else if ([primaryString rangeOfString: @"Authorization Granted"].location != NSNotFound) {
699                 //ICQ Authorization granted
700                 NSString *targetUserName = [[[[secondaryString componentsSeparatedByString:@" user "] objectAtIndex:1] componentsSeparatedByString:@" has "] objectAtIndex:0];
701                 
702                 errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ granted authorization.",nil),targetUserName];
703         }
704         
705         //If we didn't grab a translated version, at least display the English version Gaim supplied
706         [[adium interfaceController] handleMessage:([errorMessage length] ? errorMessage : primaryString)
707                                                            withDescription:([description length] ? description : ([secondaryString length] ? secondaryString : @"") )
708                                                            withWindowTitle:titleString];
709         
710         return NULL;
713 /* XXX ugly */
714 - (void *)handleNotifyFormattedWithTitle:(const char *)title primary:(const char *)primary secondary:(const char *)secondary text:(const char *)text
716         NSString *titleString = (title ? [NSString stringWithUTF8String:title] : nil);
717         NSString *primaryString = (primary ? [NSString stringWithUTF8String:primary] : nil);
718         
719         if (!titleString) {
720                 titleString = primaryString;
721                 primaryString = nil;
722         }
723         
724         NSString *secondaryString = (secondary ? [NSString stringWithUTF8String:secondary] : nil);
725         if (!primaryString) {
726                 primaryString = secondaryString;
727                 secondaryString = nil;
728         }
729         
730         static AIHTMLDecoder    *notifyFormattedHTMLDecoder = nil;
731         if (!notifyFormattedHTMLDecoder) notifyFormattedHTMLDecoder = [[AIHTMLDecoder decoder] retain];
733         NSString        *textString = (text ? [NSString stringWithUTF8String:text] : nil); 
734         if (textString) textString = [[notifyFormattedHTMLDecoder decodeHTML:textString] string];
735         
736         NSString        *description = nil;
737         if ([textString length] && [secondaryString length]) {
738                 description = [NSString stringWithFormat:@"%@\n\n%@",secondaryString,textString];
739                 
740         } else if (textString) {
741                 description = textString;
742                 
743         } else if (secondaryString) {
744                 description = secondaryString;
745                 
746         }
747         
748         NSString        *message = primaryString;
749         
750         [[adium interfaceController] handleMessage:(message ? message : @"")
751                                                            withDescription:(description ? description : @"")
752                                                            withWindowTitle:(titleString ? titleString : @"")];
754         return NULL;
758 #pragma mark File transfers
759 - (void)displayFileSendError
761         [[adium interfaceController] handleMessage:AILocalizedString(@"File Send Error",nil)
762                                                            withDescription:AILocalizedString(@"An error was encoutered sending the file.",nil)
763                                                            withWindowTitle:AILocalizedString(@"File Send Error",nil)];
766 #pragma mark Thread accessors
767 - (void)disconnectAccount:(id)adiumAccount
769         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
770         AILog(@"Setting %x disabled and offline (%s)...",account,
771                   gaim_status_type_get_id(gaim_account_get_status_type_with_primitive(account, GAIM_STATUS_OFFLINE)));
773         gaim_account_set_enabled(account, "Adium", NO);
776 - (void)registerAccount:(id)adiumAccount
778         gaim_account_register(accountLookupFromAdiumAccount(adiumAccount));
781 //Called on the gaim thread, actually performs the specified command (it should have already been tested by 
782 //attemptGaimCommandOnMessage:... above.
783 - (BOOL)doCommand:(NSString *)originalMessage
784                         fromAccount:(id)sourceAccount
785                                  inChat:(AIChat *)chat
787         GaimConversation        *conv = convLookupFromChat(chat, sourceAccount);
788         GaimCmdStatus           status;
789         char                            *markup, *error;
790         const char                      *cmd;
791         BOOL                            didCommand = NO;
793         cmd = [originalMessage UTF8String];
794         
795         //cmd+1 will be the cmd without the leading character, which should be "/"
796         markup = g_markup_escape_text(cmd+1, -1);
797         status = gaim_cmd_do_command(conv, cmd+1, markup, &error);
798         
799         //The only error status which is possible now is either 
800         switch (status) {
801                 case GAIM_CMD_STATUS_FAILED:
802                 {
803                         gaim_conv_present_error(conv->name, conv->account, "Command failed");
804                         didCommand = YES;
805                         break;
806                 }       
807                 case GAIM_CMD_STATUS_WRONG_ARGS:
808                 {
809                         gaim_conv_present_error(conv->name, conv->account, "Wrong number of arguments");
810                         didCommand = YES;                       
811                         break;
812                 }
813                 case GAIM_CMD_STATUS_OK:
814                         didCommand = YES;
815                         break;
816                 case GAIM_CMD_STATUS_NOT_FOUND:
817                 case GAIM_CMD_STATUS_WRONG_TYPE:
818                 case GAIM_CMD_STATUS_WRONG_PRPL:
819                         /* Ignore this command and let the message send; the user probably doesn't even know what they typed is a command */
820                         didCommand = NO;
821                         break;
822         }
824         return didCommand;
828  * @brief Check a message for gaim / commands=
830  * @result YES if a command was performed; NO if it was not
831  */
832 - (BOOL)attemptGaimCommandOnMessage:(NSString *)originalMessage fromAccount:(AIAccount *)sourceAccount inChat:(AIChat *)chat
834         BOOL                            didCommand = NO;
835         
836         if ([originalMessage hasPrefix:@"/"]) { 
837                 didCommand = [self doCommand:originalMessage
838                                                  fromAccount:sourceAccount
839                                                           inChat:chat];
840         }
842         return didCommand;
845 //Returns YES if the message was sent (and should therefore be displayed).  Returns NO if it was not sent or was otherwise used.
846 - (void)sendEncodedMessage:(NSString *)encodedMessage
847                            fromAccount:(id)sourceAccount
848                                         inChat:(AIChat *)chat
849                                  withFlags:(GaimMessageFlags)flags
850 {       
851         const char *encodedMessageUTF8String;
852         
853         if (encodedMessage && (encodedMessageUTF8String = [encodedMessage UTF8String])) {
854                 GaimConversation        *conv = convLookupFromChat(chat,sourceAccount);
856                 switch (gaim_conversation_get_type(conv)) {                             
857                         case GAIM_CONV_TYPE_IM: {
858                                 GaimConvIm                      *im = gaim_conversation_get_im_data(conv);
859                                 gaim_conv_im_send_with_flags(im, encodedMessageUTF8String, flags);
860                                 break;
861                         }
863                         case GAIM_CONV_TYPE_CHAT: {
864                                 GaimConvChat    *gaimChat = gaim_conversation_get_chat_data(conv);
865                                 gaim_conv_chat_send(gaimChat, encodedMessageUTF8String);
866                                 break;
867                         }
868                         
869                         case GAIM_CONV_TYPE_ANY:
870                                 GaimDebug (@"What in the world? Got GAIM_CONV_TYPE_ANY.");
871                                 break;
873                         case GAIM_CONV_TYPE_MISC:
874                         case GAIM_CONV_TYPE_UNKNOWN:
875                                 break;
876                 }
877         } else {
878                 GaimDebug (@"*** Error encoding %@ to UTF8",encodedMessage);
879         }
882 - (void)sendTyping:(AITypingState)typingState inChat:(AIChat *)chat
884         GaimConversation *conv = convLookupFromChat(chat,nil);
885         if (conv) {
886                 //              BOOL isTyping = (([typingState intValue] == AINotTyping) ? FALSE : TRUE);
888                 GaimTypingState gaimTypingState;
889                 
890                 switch (typingState) {
891                         case AINotTyping:
892                         default:
893                                 gaimTypingState = GAIM_NOT_TYPING;
894                                 break;
895                         case AITyping:
896                                 gaimTypingState = GAIM_TYPING;
897                                 break;
898                         case AIEnteredText:
899                                 gaimTypingState = GAIM_TYPED;
900                                 break;
901                 }
902         
903                 serv_send_typing(gaim_conversation_get_gc(conv),
904                                                  gaim_conversation_get_name(conv),
905                                                  gaimTypingState);
906         }       
909 - (void)addUID:(NSString *)objectUID onAccount:(id)adiumAccount toGroup:(NSString *)groupName
911         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
912         const char      *groupUTF8String, *buddyUTF8String;
913         GaimGroup       *group;
914         GaimBuddy       *buddy;
915         
916         //Find the group (Create if necessary)
917         groupUTF8String = (groupName ? [groupName UTF8String] : "Buddies");
918         if (!(group = gaim_find_group(groupUTF8String))) {
919                 group = gaim_group_new(groupUTF8String);
920                 gaim_blist_add_group(group, NULL);
921         }
922         
923         //Find the buddy (Create if necessary)
924         buddyUTF8String = [objectUID UTF8String];
925         buddy = gaim_find_buddy(account, buddyUTF8String);
926         if (!buddy) buddy = gaim_buddy_new(account, buddyUTF8String, NULL);
928         GaimDebug (@"Adding buddy %s to group %s",buddy->name, group->name);
930         /* gaim_blist_add_buddy() will move an existing contact serverside, but will not add a buddy serverside.
931          * We're working with a new contact, hopefully, so we want to call serv_add_buddy() after modifying the gaim list.
932          * This is the order done in add_buddy_cb() in gtkblist.c */
933         gaim_blist_add_buddy(buddy, NULL, group, NULL);
934         gaim_account_add_buddy(account, buddy);
937 - (void)removeUID:(NSString *)objectUID onAccount:(id)adiumAccount fromGroup:(NSString *)groupName
939         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
940         GaimBuddy       *buddy;
941         
942         if ((buddy = gaim_find_buddy(account, [objectUID UTF8String]))) {
943                 const char      *groupUTF8String;
944                 GaimGroup       *group;
946                 groupUTF8String = (groupName ? [groupName UTF8String] : "Buddies");
947                 if ((group = gaim_find_group(groupUTF8String))) {
948                         /* Remove this contact from the server-side and gaim-side lists. 
949                          * Updating gaimside does not change the server.
950                          *
951                          * Gaim has a commented XXX as to whether this order or the reverse (blist, then serv) is correct.
952                          * We'll use the order which gaim uses as of gaim 1.1.4. */
953                         gaim_account_remove_buddy(account, buddy, group);
954                         gaim_blist_remove_buddy(buddy);
955                 }
956         }
959 - (void)moveUID:(NSString *)objectUID onAccount:(id)adiumAccount toGroup:(NSString *)groupName
961         GaimAccount *account;
962         GaimBuddy       *buddy;
963         GaimGroup       *group;
964         const char      *buddyUTF8String;
965         const char      *groupUTF8String;
967         account = accountLookupFromAdiumAccount(adiumAccount);
969         //Get the destination group (creating if necessary)
970         groupUTF8String = (groupName ? [groupName UTF8String] : "Buddies");
971         group = gaim_find_group(groupUTF8String);
972         if (!group) {
973                 /* If we can't find the group, something's gone wrong... we shouldn't be using a group we don't have.
974                  * We'll just silently turn this into an add operation. */
975                 group = gaim_group_new(groupUTF8String);
976                 gaim_blist_add_group(group, NULL);
977         }
979         buddyUTF8String = [objectUID UTF8String];
980         /* If we support contacts in multiple groups at once this should change */
981         GSList *buddies = gaim_find_buddies(account, buddyUTF8String);
983         if (buddies) {
984                 GSList *cur;
985                 for (cur = buddies; cur; cur = cur->next) {
986                         /* gaim_blist_add_buddy() will update the local list and perform a serverside move as necessary */
987                         gaim_blist_add_buddy(cur->data, NULL, group, NULL);                     
988                 }
990         } else {
991                 /* If we can't find a buddy, something's gone wrong... we shouldn't be moving a buddy we don't have.
992                  * As with the group, we'll just silently turn this into an add operation. */
993                 buddy = gaim_buddy_new(account, buddyUTF8String, NULL);
995                 /* gaim_blist_add_buddy() will update the local list and perform a serverside move as necessary */
996                 gaim_blist_add_buddy(buddy, NULL, group, NULL);
997                 
998                 /* gaim_blist_add_buddy() won't perform a serverside add, however.  Add if necessary. */
999                 gaim_account_add_buddy(account, buddy);
1000         }
1003 - (void)renameGroup:(NSString *)oldGroupName onAccount:(id)adiumAccount to:(NSString *)newGroupName
1005     GaimGroup *group = gaim_find_group([oldGroupName UTF8String]);
1006         
1007         //If we don't have a group with this name, just ignore the rename request
1008     if (group) {
1009                 //Rename gaimside, which will rename serverside as well
1010                 gaim_blist_rename_group(group, [newGroupName UTF8String]);
1011         }
1014 - (void)deleteGroup:(NSString *)groupName onAccount:(id)adiumAccount
1016         GaimGroup *group = gaim_find_group([groupName UTF8String]);
1017         
1018         if (group) {
1019                 gaim_blist_remove_group(group);
1020         }
1023 #pragma mark Alias
1024 - (void)setAlias:(NSString *)alias forUID:(NSString *)UID onAccount:(id)adiumAccount
1026         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1027         if (gaim_account_is_connected(account)) {
1028                 const char  *uidUTF8String = [UID UTF8String];
1029                 GaimBuddy   *buddy = gaim_find_buddy(account, uidUTF8String);
1030                 const char  *aliasUTF8String = [alias UTF8String];
1031                 const char      *oldAlias = (buddy ? gaim_buddy_get_alias(buddy) : nil);
1032         
1033                 if (buddy && ((aliasUTF8String && !oldAlias) ||
1034                                           (!aliasUTF8String && oldAlias) ||
1035                                           ((oldAlias && aliasUTF8String && (strcmp(oldAlias,aliasUTF8String) != 0))))) {
1037                         gaim_blist_alias_buddy(buddy,aliasUTF8String);
1038                         serv_alias_buddy(buddy);
1039                         
1040                         //If we had an alias before but no longer have, adiumGaimBlistUpdate() is not going to send the update
1041                         //(Because normally it's wasteful to send a nil alias to the account).  We need to manually invoke the update.
1042                         if (oldAlias && !alias) {
1043                                 AIListContact *theContact = contactLookupFromBuddy(buddy);
1044                                 
1045                                 [adiumAccount updateContact:theContact
1046                                                                         toAlias:nil];
1047                         }
1048                 }
1049         }
1052 #pragma mark Chats
1053 - (void)openChat:(AIChat *)chat onAccount:(id)adiumAccount
1055         //Looking up the conv from the chat will create the GaimConversation gaimside, joining the chat, opening the server
1056         //connection, or whatever else is done when a chat is opened.
1057         convLookupFromChat(chat,adiumAccount);
1060 - (void)closeChat:(AIChat *)chat
1062         GaimConversation *conv = existingConvLookupFromChat(chat);
1064         if (conv) {
1065                 //We use chatDict's objectfor the passed chatUniqueID because we can no longer trust any other
1066                 //values due to threading potentially letting them have changed on us.
1067                 [chatDict removeObjectForKey:[chat uniqueChatID]];
1068                         
1069                 //We retained the chat when setting it as the ui_data; we are releasing here, so be sure to set conv->ui_data
1070                 //to nil so we don't try to do it again.
1071                 [(AIChat *)conv->ui_data release];
1072                 conv->ui_data = nil;
1073                 
1074                 //Tell gaim to destroy the conversation.
1075                 gaim_conversation_destroy(conv);
1076         }       
1079 - (void)inviteContact:(AIListContact *)listContact toChat:(AIChat *)chat withMessage:(NSString *)inviteMessage;
1081         GaimConversation        *conv;
1082         GaimAccount                     *account;
1083         GaimConvChat            *gaimChat;
1084         AIAccount                       *adiumAccount = [chat account];
1085         
1086         GaimDebug (@"#### inviteContact:%@ toChat:%@",[listContact UID],[chat name]);
1087         // dchoby98
1088         if (([adiumAccount isKindOfClass:[CBGaimAccount class]]) &&
1089            (conv = convLookupFromChat(chat, adiumAccount)) &&
1090            (account = accountLookupFromAdiumAccount((CBGaimAccount *)adiumAccount)) &&
1091            (gaimChat = gaim_conversation_get_chat_data(conv))) {
1093                 //GaimBuddy             *buddy = gaim_find_buddy(account, [[listObject UID] UTF8String]);
1094                 GaimDebug (@"#### addChatUser chat: %@ (%@) buddy: %@",[chat name], chat,[listContact UID]);
1095                 serv_chat_invite(gaim_conversation_get_gc(conv),
1096                                                  gaim_conv_chat_get_id(gaimChat),
1097                                                  (inviteMessage ? [inviteMessage UTF8String] : ""),
1098                                                  [[listContact UID] UTF8String]);
1099                 
1100         }
1103 - (void)createNewGroupChat:(AIChat *)chat withListContact:(AIListContact *)contact
1105         //Create the chat
1106         convLookupFromChat(chat, [chat account]);
1107         
1108         //Invite the contact, with no message
1109         [self inviteContact:contact toChat:chat withMessage:nil];
1112 #pragma mark Account Status
1113 - (void)setStatusID:(const char *)statusID 
1114                    isActive:(NSNumber *)isActive
1115                   arguments:(NSMutableDictionary *)arguments
1116                   onAccount:(id)adiumAccount
1118         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1119         GList           *attrs = NULL;
1121         //Generate a GList of attrs from arguments
1122         if ([arguments count]) {
1123                 NSEnumerator    *enumerator;
1124                 NSString                *key;
1125                 
1126                 enumerator = [arguments keyEnumerator];
1127                 while ((key = [enumerator nextObject])) {
1128                         const char *value = NULL;
1129                         id       valueObject;
1131                         valueObject = [arguments objectForKey:key];
1132                         
1133                         if ([valueObject isKindOfClass:[NSNumber class]]) {
1134                                 value = GINT_TO_POINTER([valueObject intValue]);
1136                         } else if ([valueObject isKindOfClass:[NSString class]]) {
1137                                 value = [valueObject UTF8String];
1138                         }                               
1139                         
1140                         if (value) {
1141                                 //Append the key
1142                                 attrs = g_list_append(attrs, (gpointer)[key UTF8String]);
1143                                 
1144                                 //Now append the value
1145                                 attrs = g_list_append(attrs, (gpointer)value);
1147                         } else {
1148                                 AILog(@"Warning; could not determine value of %@ for key %@, statusID %s",valueObject,key,statusID);
1149                         }
1150                 }
1151         }
1153         AILog(@"Setting status on %x (%s): ID %s, isActive %i, attributes %@",account, gaim_account_get_username(account),
1154                   statusID, [isActive boolValue], arguments);
1155         gaim_account_set_status_list(account, statusID, [isActive boolValue], attrs);
1157         if (gaim_status_is_online(gaim_account_get_active_status(account)) &&
1158                 gaim_account_is_disconnected(account))  {
1159                 //This status is an online status, but the account is not connected or connecting
1161                 //Ensure the account is enabled
1162                 if (!gaim_account_get_enabled(account, "Adium")) {
1163                         gaim_account_set_enabled(account, "Adium", YES);
1164                 }
1166                 //Now connect the account
1167                 gaim_account_connect(account);
1168         }
1169         
1172 - (void)setInfo:(NSString *)profileHTML onAccount:(id)adiumAccount
1174         GaimAccount     *account = accountLookupFromAdiumAccount(adiumAccount);
1175         const char *profileHTMLUTF8 = [profileHTML UTF8String];
1177         gaim_account_set_user_info(account, profileHTMLUTF8);
1179         if (account->gc != NULL && gaim_account_is_connected(account)) {
1180                 serv_set_info(account->gc, profileHTMLUTF8);
1181         }
1184 - (void)setBuddyIcon:(NSString *)buddyImageFilename onAccount:(id)adiumAccount
1186         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1187         if (account) {
1188                 gaim_account_set_buddy_icon(account, [buddyImageFilename UTF8String]);
1189         }
1192 - (void)setIdleSinceTo:(NSDate *)idleSince onAccount:(id)adiumAccount
1194         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1195         if (gaim_account_is_connected(account)) {
1196                 NSTimeInterval idle = (idleSince != nil ? [idleSince timeIntervalSince1970] : 0);
1197                 GaimPresence *presence;
1199                 presence = gaim_account_get_presence(account);
1201                 gaim_presence_set_idle(presence, (idle > 0), idle);
1202         }
1205 #pragma mark Get Info
1206 - (void)getInfoFor:(NSString *)inUID onAccount:(id)adiumAccount
1208         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1209         if (gaim_account_is_connected(account)) {
1210                 
1211                 serv_get_info(account->gc, [inUID UTF8String]);
1212         }
1215 #pragma mark Xfer
1216 - (void)xferRequest:(GaimXfer *)xfer
1218         gaim_xfer_request(xfer);
1221 - (void)xferRequestAccepted:(GaimXfer *)xfer withFileName:(NSString *)xferFileName
1223         //Only start the file transfer if it's still not marked as cancelled and therefore can be begun.
1224         if ((gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_CANCEL_LOCAL) &&
1225                 (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_CANCEL_REMOTE)) {
1226                 //XXX should do further error checking as done by gaim_xfer_choose_file_ok_cb() in gaim's ft.c
1227                 gaim_xfer_request_accepted(xfer, [xferFileName UTF8String]);
1228         }
1231 - (void)xferRequestRejected:(GaimXfer *)xfer
1233         gaim_xfer_request_denied(xfer);
1236 - (void)xferCancel:(GaimXfer *)xfer
1238         if ((gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_UNKNOWN) ||
1239                 (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_NOT_STARTED) ||
1240                 (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_STARTED) ||
1241                 (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_ACCEPTED)) {
1242                 gaim_xfer_cancel_local(xfer);
1243         }
1246 #pragma mark Account settings
1247 - (void)setCheckMail:(NSNumber *)checkMail forAccount:(id)adiumAccount
1249         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1250         BOOL            shouldCheckMail = [checkMail boolValue];
1252         gaim_account_set_check_mail(account, shouldCheckMail);
1255 - (void)setDefaultPermitDenyForAccount:(id)adiumAccount
1257         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1259         if (account && gaim_account_get_connection(account)) {
1260                 account->perm_deny = GAIM_PRIVACY_DENY_USERS;
1261                 serv_set_permit_deny(gaim_account_get_connection(account));
1262         }       
1265 #pragma mark Protocol specific accessors
1266 #ifndef JOSCAR_SUPERCEDE_LIBGAIM
1267 - (void)OSCAREditComment:(NSString *)comment forUID:(NSString *)inUID onAccount:(id)adiumAccount
1269         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1270         if (gaim_account_is_connected(account)) {
1271                 GaimBuddy   *buddy;
1272                 GaimGroup   *g;
1273                 OscarData   *od;
1275                 const char  *uidUTF8String = [inUID UTF8String];
1277                 if ((buddy = gaim_find_buddy(account, uidUTF8String)) &&
1278                         (g = gaim_buddy_get_group(buddy)) && 
1279                         (od = account->gc->proto_data)) {
1280                         aim_ssi_editcomment(od, g->name, uidUTF8String, [comment UTF8String]);  
1281                 }
1282         }
1285 - (void)OSCARSetFormatTo:(NSString *)inFormattedUID onAccount:(id)adiumAccount
1287         GaimAccount *account = accountLookupFromAdiumAccount(adiumAccount);
1289         if (account &&
1290                 gaim_account_is_connected(account) &&
1291                 [inFormattedUID length]) {
1292                 
1293                 oscar_reformat_screenname(gaim_account_get_connection(account), [inFormattedUID UTF8String]);
1294         }
1296 #endif
1298 #pragma mark Request callbacks
1300 - (void)performContactMenuActionFromDict:(NSDictionary *)dict 
1302         GaimMenuAction  *act = [[dict objectForKey:@"GaimMenuAction"] pointerValue];
1303         GaimBuddy               *buddy = [[dict objectForKey:@"GaimBuddy"] pointerValue];
1305         //Perform act's callback with the desired buddy and data
1306         if (act->callback)
1307                 ((void (*)(void *, void *))act->callback)((GaimBlistNode *)buddy, act->data);
1310 - (void)performAccountMenuActionFromDict:(NSDictionary *)dict
1312         GaimPluginAction        *pam = [[dict objectForKey:@"GaimPluginAction"] pointerValue];
1314         if (pam->callback)
1315                 pam->callback(pam);
1319 * @brief Call the gaim callback to finish up the window
1321  * @param inCallBackValue The cb to use
1322  * @param inUserDataValue Original user data
1323  */
1324 - (void)doAuthRequestCbValue:(NSValue *)inCallBackValue withUserDataValue:(NSValue *)inUserDataValue 
1325 {       
1326         GaimAccountRequestAuthorizationCb callBack = [inCallBackValue pointerValue];
1327         if (callBack) {
1328                 callBack([inUserDataValue pointerValue]);
1329         }
1332 #pragma mark Secure messaging
1334 - (void)gaimConversation:(GaimConversation *)conv setSecurityDetails:(NSDictionary *)securityDetailsDict
1338 - (void)refreshedSecurityOfGaimConversation:(GaimConversation *)conv
1340         GaimDebug (@"*** Refreshed security...");
1343 - (void)dealloc
1345         gaim_signals_disconnect_by_handle(adium_gaim_get_handle());
1347         [super dealloc];
1351  //This doesn't work for several reasons.  The biggest: libgaim expects strings to be translated immediately;
1352  //substitutions have already occurred, as of concatenations, because we see them.
1353 #pragma mark Translation
1355 - (NSString *)localizedGaimString:(NSString *)inString
1357         static BOOL configuredGettext = NO;
1358         if (!configuredGettext) {
1359                 bindtextdomain("libgaim", [[[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"potfiles"] UTF8String]);
1360                 bind_textdomain_codeset("libgaim", "UTF-8");
1361                 
1362                 //Change language.
1363                 NSString        *preferredLocalization = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];
1364                 setenv("LANGUAGE", [preferredLocalization UTF8String], 1);
1365                 AILog(@"Gaim translation using %s",[preferredLocalization UTF8String]);
1367                 //Make change known. _nl_msg_cat_cntr is an external defined in gettext's loadmsgcat.c
1368                 {
1369                         extern int  _nl_msg_cat_cntr;
1370                         ++_nl_msg_cat_cntr;
1371                 }
1372         }
1373         
1374         return [NSString stringWithUTF8String:dgettext("libgaim", [inString UTF8String])];
1378 @end